package asteroids;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;

/**
 * This abstract class represents a space entity of the arcade game Asteroids.
 * A space entity can be a ship, an asteroid or a bullet.
 * 
 * @invar	The position of the space entity must be a valid position.
 * 			| !getPosition().getX().isNaN() && !getPosition().getY().isNaN()
 * @invar	The speed of the space entity must always have a valid value.
 * 			| isValidVelocity(getVelocity().getX(),getVelocity().getY())
 * @invar	The maximum speed of the space entity shall never exceed the speed of light.
 * 			| getMaxSpeed() <= 300000
 * @invar	The radius must always be a valid radius.
 * 			| isValidRadius(getRadius())
 * @invar	The minimum radius must always be at least zero.
 * 			| MINIMUM_RADIUS >= 0
 * @invar	The mass of the space entity must always have a valid value.
 * 			| isValidMass(getMass())
 * @invar	The world of the space entity must be a valid world.
 * 			| isValidWorld(getWorld())
 *
 * @author	Antoine Lammens & Yannick Laevaert
 * @version 2.0
 *
 */
public abstract class SpaceEntity {
	
	/**
	 * Variable registering the position of the space entity expressed in kilometres.
	 * 		| position[0] = x-position
	 * 		| position[1] = y-position
	 */
	private Vector position;

	/**
	 * Variable registering the velocity of the space entity expressed in kilometres per second.
	 * 		| velocity[0] = x-velocity
	 * 		| velocity[1] = y-velocity
	 */
	private Vector velocity;
	
	/**
	 * Variable registering the mass of the space entity expressed in kilograms.
	 */
	private final double mass;

	/**
	 * Variable registering the radius of the space entity in kilometres.
	 */
	private final double radius;

	/**
	 * Constant reflecting the lowest possible value for the radius of a space entity.
	 * 
	 * @return	The lowest possible value for the radius of a space entity.
	 * 			| result == 0
	 */
	public static final double MINIMUM_RADIUS = 0.0;

	/**
	 * Final variable registering the maximum speed of a space entity.
	 */
	protected final double MAX_SPEED;
	
	/**
	 * Variable registering the world of the space entity, if any.
	 */
	private World world;
	
	/**
	 * Variable registering if a space entity is terminated.
	 */
	private boolean isTerminated;
	
	/**
	 * Initialize a space entity with default values.
	 * 
	 * @effect	Initialize this space entity with positions at 0 km, velocities at 0 km/s, a mass of 1000 kg, 
	 * 			as radius the minimum radius and without a world.
	 * 			| this(0.0, 0.0, 0.0, 0.0, 1000.0, MINIMUM_RADIUS, null)
	 */
	protected SpaceEntity() {
		this(0.0, 0.0, 0.0, 0.0, 1000.0, MINIMUM_RADIUS, null);
	}

	/**
	 * Initialize this space entity with given positions, given velocities, given mass, given radius and given world. 
	 * The max speed is set on the speed of light.
	 * 
	 * @param 	xPosition
	 * 			The x-position for this space entity.
	 * @param 	yPosition
	 * 			The y-position for this space entity.
	 * @param 	xVelocity
	 * 			The x-velocity for this space entity.
	 * @param 	yVelocity
	 * 			The y-velocity for this space entity.
	 * @param	mass
	 * 			The mass of this space entity.
	 * @param 	radius
	 * 			The radius for this space entity.
	 * @param	world
	 * 			The world for this space entity.
	 * @post	The max speed of the space entity is set to the default value: the speed of light.
	 * 			| new.getMaxSpeed() = 300000
	 * @post	The new x-position is equal to the given value.
	 * 			| new.getPosition().getX() == xPosition
	 * @post	The new y-position is equal to the given value.
	 * 			| new.getPosition().getY() == yPosition
	 * @post	The max speed is initialized at the speed of light.
	 * 			| new.getMaxSpeed() = 300 000
	 * @post	If the given velocities are valid, the velocities of this space entity are equal to the given velocities.
	 * 			If the general velocity would exceed the maximum speed of the space entity, the general velocity equals this maximum speed.
	 * 			| if(isValidVelocity(xVelocity, yVelocity)
	 * 			|	then new.getVelocity().getX() == xVelocity &&
	 * 			|		 new.getVelocity().getY() == yVelocity
	 * 			|	else new.getGeneralVelocity() == getMaxSpeed()
	 * @pre		The new mass must be a valid mass.
	 * 			| isValidMass(mass)
	 * @post	The new mass is equal to the given value.
	 * 			| new.getMass() == mass
	 * @post	The new radius is equal to the given value.
	 * 			| new.getRadius() == radius
	 * @post	The new world is set to the given value.
	 * 			| new.getWorld() == world
	 * @post	The space entity is not yet terminated.
	 * 			| new.isTerminated() == false
	 * @throws	IllegalArgumentException
	 * 			The given radius is not valid radius.
	 * 			| !isValidRadius(radius) 
	 */
	protected SpaceEntity(double xPosition, double yPosition, double xVelocity, double yVelocity, 
			double mass, double radius, World world) {
		if (!isValidRadius(radius)) {
			throw new IllegalArgumentException("No valid radius");
		}
		
		position = new Vector(2);
		velocity = new Vector(2);
		
		this.setPosition(xPosition, yPosition);
		
		this.MAX_SPEED = 300000.0;
		this.setVelocity(xVelocity, yVelocity);
		if (!isValidVelocity(this.getVelocity().getX(), this.getVelocity().getY())) {
			setMaxSpeedWithSameDirection();
		}
		
		this.mass = mass;
		
		this.radius = radius;
		
		this.setWorld(world);
		
		setTerminated(false);
	}
	
	
	/**
	 * Return the position of the space entity.
	 */
	@Basic
	public Vector getPosition() {
		return position;
	}

	/**
	 * Set the position of the space entity to the given values.
	 * 
	 * @param	xPosition
	 * 			The new xPosition for the space entity.
	 * @param	yPosition
	 * 			The new yPosition for the space entity.
	 * @post	The position of the space entity is equal to the given values.
	 * 			| new.getPosition().getX() == xPosition
	 * 			| new.getPosition().getY() == yPosition
	 * @throws	IllegalArgumentException
	 * 			xPosition == null || xPosition.isNaN() || yPosition == null || yPosition.isNaN()
	 */
	public void setPosition(Double xPosition, Double yPosition) throws IllegalArgumentException {
		if (xPosition == null || xPosition.isNaN() || yPosition == null || yPosition.isNaN())
			throw new IllegalArgumentException();
		else {
			getPosition().setX(xPosition);
			getPosition().setY(yPosition);
		}
	}
	
	/**
	 * Return the max speed of the space entity.
	 */
	@Basic @Immutable
	public double getMaxSpeed() {
		return MAX_SPEED;
	}

	/**
	 * Return the velocity of the space entity.
	 */
	@Basic
	public Vector getVelocity() {
		return velocity;
	}

	/**
	 * Set the velocity of the space entity to the given velocity.
	 * 
	 * @param	xVelocity
	 * 			The new xVelocity for the space entity.
	 * @param	yVelocity
	 * 			The new yVelocity for the space entity.
	 * @post	If xVelocity is a valid number, the xVelocity of the space entity is set to this value.
	 * 			Otherwise, it is set to 0.
	 * 			| if (xVelocity == null || xVelocity.isNaN())
	 * 			|	then new.getVelocity().getX() == 0
	 * 			|   else new.getVelocity().getX() == xVelocity
	 * @post	If yVelocity is a valid number, the yVelocity of the space entity is set to this value.
	 * 			Otherwise, it is set to 0.
	 * 			| if (yVelocity == null || yVelocity.isNaN())
	 * 			|	then new.getVelocity().getY() == 0
	 * 			|   else new.getVelocity().getY() == yVelocity
	 */
	public void setVelocity(Double xVelocity, Double yVelocity) {
		if (xVelocity == null || xVelocity.isNaN())
			getVelocity().setX(0.0);
		else
			getVelocity().setX(xVelocity);
		if (yVelocity == null || yVelocity.isNaN())
			getVelocity().setY(0.0);
		else
			getVelocity().setY(yVelocity);
	}
	
	/**
	 * Check whether the given velocities result in a valid speed.
	 * 
	 * @param	xVelocity
	 * 			The velocity in the x-direction.
	 * @param	yVelocity
	 * 			The velocity in the y-direction.
	 * @return	True if and only if the given velocities are valid numbers and 
	 * 			the general velocity does not exceed the maximum speed of the space entity.
	 * 			| result == getGeneralVelocity(xVelocity,yVelocity) <= getMaxSpeed() 
	 * 			|				&& !xVelocity.isNaN() && !yVelocity.isNaN() 
	 */
	public boolean isValidVelocity(Double xVelocity, Double yVelocity){
		if (xVelocity == null || yVelocity == null || xVelocity.isNaN() || yVelocity.isNaN())
			return false;
		else
			return Util.fuzzyLessThanOrEqualTo(getGeneralVelocity(xVelocity,yVelocity), getMaxSpeed());
	}

	/**
	 * Calculate the general velocity of the space entity.
	 * 
	 * @param	xVelocity
	 * 			The velocity in the x-direction.
	 * @param	yVelocity
	 * 			The velocity in the y-direction.
	 * @return	The general velocity is the square root of the x-velocity to the power of 2 plus 
	 * 			the y-velocity to the power of 2.
	 * 			| result == Math.sqrt(Math.pow(xVelocity,2) + Math.pow(yVelocity,2))
	 * @note	If something goes wrong, an invalid velocity will be returned.
	 * 			| result == this.getMaxSpeed() + 1
	 */
	public double getGeneralVelocity(double xVelocity, double yVelocity) {
		try{
			return Math.sqrt(Math.pow(xVelocity,2)+Math.pow(yVelocity,2));
		}
		catch (Exception exc) {
			return getMaxSpeed() + 1;
		}
	}

	/**
	 * Return the radius of the space entity.
	 */
	@Basic @Immutable
	public double getRadius(){
		return radius;
	}

	/**
	 * Check whether the given radius is a valid radius.
	 * 
	 * @param 	radius
	 * 			The radius to check.
	 * @return 	True if and only if the given radius is a valid number, finite and 
	 * 			is greater than or equal to the minimum radius.
	 * 			| result == radius >= MINIMUM_RADIUS && radius <= Double.MAX_VALUE && !radius.isNaN()
	 */
	public static boolean isValidRadius(Double radius) {
		if (radius.isNaN())
			return false;
		else
			return (radius >= MINIMUM_RADIUS) && (radius <= Double.MAX_VALUE);
	}

	/**
	 * Return the mass of the space entity.
	 */
	@Basic
	public double getMass(){
		return mass;
	}

	/**
	 * Check whether the given mass is a valid mass.
	 * 
	 * @param 	mass
	 * 			The mass to check.
	 * @return 	True if and only if the given mass is a valid number and is strictly positive.
	 * 			| result == mass > 0 && !mass.isNaN()
	 */
	public static boolean isValidMass(Double mass) {
		if (mass.isNaN())
			return false;
		else
			return (mass > 0);
	}
	
	/**
	 * Return the world of the space entity.
	 */
	@Basic
	public World getWorld() {
		return world;
	}

	/**
	 * Set the world of the space entity to the given parameter.
	 * 
	 * @param	world
	 * 			The new world for the space entity.
	 * @post	The world of the space entity is equal to the given parameter.
	 * 			| new.getWorld() == world
	 */
	public void setWorld(World world) {
		this.world = world;
	}

	/**
	 * Check whether the given world is a valid world.
	 * 
	 * @param 	world
	 * 			The world to check.
	 * @return 	True if and only if the given world is null or equal to the present world.
	 * 			| result == getWorld() == null || this.getWorld() == world
	 */
	public boolean isValidWorld(World world) {
		return getWorld() == null || this.getWorld() == world;
	}
	
	/**
	 * Change the position of the space entity based on the current position, 
	 * velocity and a given time duration dt in seconds.
	 * 
	 * @param 	dt
	 * 			The time duration in seconds.
	 * @post	The new x-position is the old x-position added with 
	 * 			the x-velocity multiplied with the time duration.
	 * 			| new.getPosition().getX() == getPosition().getX() + getVelocity().getX() * dt 	
	 * @post	The new y-position is the old y-position added with 
	 * 			the y-velocity multiplied with the time duration.
	 * 			| new.getPosition().getY() == getPosition().getY() + getVelocity().getY() * dt 	
	 * @throws	IllegalArgumentException
	 * 			The time duration is invalid.
	 * 			| dt < 0 || dt == null || dt.isNaN()
	 */
	public void move(Double dt) throws IllegalArgumentException{
		if (dt == null || dt.isNaN() || dt < 0)
			throw new IllegalArgumentException();
		else
			setPosition(getPosition().getX() + getVelocity().getX() * dt, getPosition().getY() + getVelocity().getY() * dt);
	}
	
	/**
	 * Return the distance in between two space entities. The absolute value of the result of this method is
	 * the minimum distance either space entity should move such that both entities are adjacent.
	 * Note that the result must be negative if the entities overlap. The distance between a space entity and itself is 0.
	 *
	 * @param 	spaceEntity
	 * 			The space entity from which the distance will be calculated.
	 * @return 	The distance between the edges of the two space entities. The distance between a space entity and itself is 0.
	 * 			| if (this == spaceEntity) 
	 *			|	then result == 0.0 
	 *			|	else result == Math.sqrt(Math.pow(this.getX()-spaceEntity.getX(),2) + Math.pow(this.getY()-spaceEntity.getY(),2))
	 *			|					- (this.getRadius()+spaceEntity.getRadius())
	 * @throws	NullPointerException
	 * 			The other space entity is null.
	 * 			| spaceEntity == null
	 * @throws	IllegalStateException
	 * 			The calculations result in an invalid number.
	 */
	public double getDistanceBetween(SpaceEntity spaceEntity) throws NullPointerException, IllegalStateException {
			try {
				if (this == spaceEntity)
					return 0.0;
				return (Math.sqrt(Math.pow(this.getPosition().getX() - spaceEntity.getPosition().getX(), 2)
						+ Math.pow(this.getPosition().getY() - spaceEntity.getPosition().getY(), 2)) - (this
						.getRadius() + spaceEntity.getRadius()));
			} catch (NullPointerException nullexc) {
				throw new NullPointerException();
			} catch (Exception exc) {
				throw new IllegalStateException();
			}
	}

	/**
	 * Check whether two space entities overlap. 
	 * 
	 * @param 	spaceEntity
	 * 			The space entity from which has to be checked if it overlaps with the current space entity.
	 * @return	True if and only if two space crafts overlap. A space entity always overlaps with itself. If two space crafts touch, the result is false.
	 * 			| if (this == spaceEntity) 
				|	then result == true 
				|   else result == getDistanceBetween(spaceEntity) < 0
	 * @throws	NullPointerException
	 * 			The other space entity is null.
	 * 			| spaceEntity == null	
	 * @throws	IllegalStateException
	 * 			The calculations result in an invalid number.
	 */
	public boolean overlap(SpaceEntity spaceEntity) throws NullPointerException, IllegalStateException {
		if (this == spaceEntity) 
			return true;
		return getDistanceBetween(spaceEntity) < 0.0;
	}

	/**
	 * Return when, if ever, two space crafts will collide.
	 * 
	 * @param 	spaceEntity
	 * 			The space entity from which the time until collision has to be calculated.
	 * @return  If the resulting time is finite, the distance between both
	 *          space entities would be fuzzy equal to zero if they would both move
	 *          during the resulting time.
	 *          | if (result < Double.POSITIVE_INFINITY) then
	 *       	|   Util.fuzzyEquals(this.move(result).getDistanceBetween(spaceEntity.move(result)),0.0)
	 * @return  If the resulting distance is finite, the distance between both space entities
	 *          would be fuzzy different from zero if they would move for a time shorter than the
	 *          resulting time.
	 *      	| if (result < Double.POSITIVE_INFINITY) then
	 *      	|   for each time in 0.0..result:
	 *    	    |     if (time < result)
	 *    	    |       then ! Util.fuzzyEquals(this.move(time).getDistanceBetween(spaceEntity.move(time)),0.0)
	 * @return  If the resulting time is infinite, this space entity is the same as the
	 *          given space entity or the distance between both
	 *          space entities would be different from zero for each finite time they would move.
	 *   	    | if (result == Double.POSITIVE_INFINITY) then
	 *      	|   (this == other) ||
	 *      	|   (for each time in 0.0..Double.POSITIVE_INFINITY:
	 *        	|     if (! Double.isInfinite(time)) then
	 *       	|       (! Util.fuzzyEquals(this.move(time).getDistanceBetween(spaceEntity.move(time)),0.0))
	 * @return	If one of the space entities is the source of the other space entity (by definition a bullet in that case), the resulting time is infinite.
	 * 			| if (spaceEntity.isSourceOf(this) || this.isSourceOf(spaceEntity))
				|	then result == Double.POSITIVE_INFINITY
	 * @throws	NullPointerException
	 * 			The other space entity is null.
	 * 			| spaceEntity == null 		
	 * @throws	IllegalStateException
	 * 			The calculations result in an invalid number.	
	 */
	public double getTimeToCollision(SpaceEntity spaceEntity)
			throws NullPointerException, IllegalStateException {
		try {
			Vector deltaR = Vector.substract(this.getPosition(), spaceEntity.getPosition());
			Vector deltaV = Vector.substract(this.getVelocity(), spaceEntity.getVelocity());
			double d = Math.pow(Vector.dotProduct(deltaV, deltaR), 2) - (Vector.dotProduct(deltaV, deltaV))
					* (Vector.dotProduct(deltaR, deltaR) - Math.pow(this.getRadius() + spaceEntity.getRadius(), 2));
			if (Util.fuzzyGreaterThanOrEqualTo(Vector.dotProduct(deltaV, deltaR), 0)
					|| Util.fuzzyLessThanOrEqualTo(d, 0))
				return Double.POSITIVE_INFINITY;
			else if (spaceEntity.isSourceOf(this) || this.isSourceOf(spaceEntity))
				return Double.POSITIVE_INFINITY;
			else
				return -(Vector.dotProduct(deltaV, deltaR) + Math.sqrt(d)) / Vector.dotProduct(deltaV, deltaV);
		} catch (NullPointerException nullexc) {
			throw new NullPointerException();
		} catch (Exception exc) {
			throw new IllegalStateException();
		}
	}
		
	/**
	 * Return the time to the first collision of a space entity with a boundary of his world, if he has a world.
	 * 
	 * @return  If the resulting time is finite, the distance between this space entity and a boundary would be fuzzy equal to zero if it would move
	 *          during the resulting time.
	 *          | if (result < Double.POSITIVE_INFINITY) then
	 *       	|   Util.fuzzyEquals(this.move(result).getPosition().getX() - this.getRadius() ,0.0) ||
	 *       	|	Util.fuzzyEquals(this.getWorld().getWidth() - this.move(result).getPosition().getX(), this.getRadius()) ||
	 *       	|	Util.fuzzyEquals(this.getWorld().getHeight() - this.move(result).getPosition().getY(), this.getRadius()) || 
	 *       	|	Util.fuzzyEquals(this.move(result).getPosition().getY(), this.getRadius())
	 * @return  If the resulting distance is finite, the distance between this ship and a boundary
	 *          would be fuzzy different from zero if it would move for a time shorter than the
	 *          resulting time.
	 *      	| if (result < Double.POSITIVE_INFINITY) then
	 *      	|   for each time in 0.0..result:
	 *    	    |     if (time < result) then
	 *       	|   !Util.fuzzyEquals(this.move(result).getPosition().getX() - this.getRadius() ,0.0) &&
	 *       	|	!Util.fuzzyEquals(this.getWorld().getWidth() - this.move(result).getPosition().getX(), this.getRadius()) &&
	 *       	|	!Util.fuzzyEquals(this.getWorld().getHeight() - this.move(result).getPosition().getY(), this.getRadius()) &&
	 *       	|	!Util.fuzzyEquals(this.move(result).getPosition().getY(), this.getRadius())
	 * @return  If the resulting time is infinite, this space entity has zero velocity.
	 *   	    | if (result == Double.POSITIVE_INFINITY) then
	 *      	|   this.getVelocity().getX() == 0 && this.getVelocity().getY() == 0
	 * @throws 	IllegalStateException
	 * 			| getWorld() == null
	 */
	public double getTimeToBoundaryCollision() throws IllegalStateException {
		if(getWorld() != null) {
			
			double timeLeft = (- this.getPosition().getX() + this.getRadius()) / this.getVelocity().getX();
			double timeRight = (this.getWorld().getWidth() - this.getPosition().getX() - this.getRadius()) / this.getVelocity().getX();
			double timeHorizontalBoundary = Math.max(timeLeft, timeRight);
			
			double timeDown = (- this.getPosition().getY() + this.getRadius()) / this.getVelocity().getY();
			double timeUp = (this.getWorld().getHeight() - this.getPosition().getY() - this.getRadius()) / this.getVelocity().getY();
			double timeVerticalBoundary = Math.max(timeDown, timeUp);
			
			return Math.min(timeHorizontalBoundary, timeVerticalBoundary);
		}
		else
			throw new IllegalStateException();
	}
	
	/**
	 * Return where, if ever, two space crafts will collide.
	 * 
	 * @param	spaceEntity
	 * 			The space entity from which the collision position has to be calculated.
	 * @return	Return the first position where this space entity and the given space entity collide, or null if they never collide. 
	 * 			A space entity never collides with itself. The result of this method is either null or an array of length 2, 
	 * 			where the element at index 0 represents the x-coordinate and the element at index 1 represents the y-coordinate.
	 * 			| if (this.getTimeToCollision(spaceEntity) == Double.POSITIVE_INFINITY)
	 * 			| 	then result == null
	 * 			|	else result == this.move(getTimeToCollision(spaceEntity)).getPosition() - 
	 * 			|					(this.move(getTimeToCollision(spaceEntity)).getPosition() - spaceEntity.move(getTimeToCollision(spaceEntity)).getPosition())
	 * 			|					* this.getRadius() / (this.getRadius() + spaceEntity.getRadius())
	 * @throws	NullPointerException
	 * 			The other space entity is null.
	 * 			| spaceEntity == null
	 * @throws	IllegalStateException
	 * 			The calculations result in an invalid number.
	 */
	public Vector getCollisionPosition(SpaceEntity spaceEntity)
			throws NullPointerException, IllegalStateException {
		if (this.getTimeToCollision(spaceEntity) == Double.POSITIVE_INFINITY)
			return null;
		else {
			double timeToCollision = getTimeToCollision(spaceEntity);
			double scalar = this.getRadius() / (this.getRadius() + spaceEntity.getRadius());
			Vector posSimulationEntity = Vector.sum(this.getPosition(), Vector.multiply(this.getVelocity(), timeToCollision));
			Vector posInteractionEntity = Vector.sum(spaceEntity.getPosition(), Vector.multiply(spaceEntity.getVelocity(), timeToCollision));
			Vector deltaPos = Vector.substract(posSimulationEntity, posInteractionEntity);
			return Vector.substract(posSimulationEntity, Vector.multiply(deltaPos, scalar));
		}
	}
	
	/**
	 * Bounce off a boundary of the space entity's world, if it has a world.
	 * 
	 * @post	If the space entity touches the bottom boundary or the top boundary, the y-velocity of the space entity is negated.
	 * 			| if(this.getWorld() != null && (Util.fuzzyEquals(this.getWorld().getHeight() - this.getPosition().getY(), this.getRadius())
	 *			|		|| Util.fuzzyEquals(this.getPosition().getY(), this.getRadius()))) then
	 *			|	new.getVelocity().getY() == - getVelocity().getY()
	 * @post	If the space entity touches the left boundary or the right boundary, the x-velocity of the space entity is negated.
	 * 			| if(this.getWorld() != null && (Util.fuzzyEquals(this.getWorld().getWidth() - this.getPosition().getX(), this.getRadius())
	 *			|		|| Util.fuzzyEquals(this.getPosition().getX(), this.getRadius()))) then
	 *			|	new.getVelocity().getX() == - getVelocity().getX()
	 * @throws	IllegalStateException
	 * 			| this.getWorld() == null
	 */
	public void bounce() throws IllegalStateException {
		if(this.getWorld() != null) {
			//Horizontal boundary
			if(Util.fuzzyEquals(this.getWorld().getHeight() - this.getPosition().getY(), this.getRadius())
					|| Util.fuzzyEquals(this.getPosition().getY(), this.getRadius())) {
				this.setVelocity(this.getVelocity().getX(), - this.getVelocity().getY());
			}
			//Vertical boundary
			if(Util.fuzzyEquals(this.getWorld().getWidth() - this.getPosition().getX(), this.getRadius())
					|| Util.fuzzyEquals(this.getPosition().getX(), this.getRadius())) {
				this.setVelocity(- this.getVelocity().getX(), this.getVelocity().getY());
			}				
		}
		else
			throw new IllegalStateException();
	}
	
	/**
	 * Return whether a space entity is terminated.
	 */
	@Basic
	public boolean isTerminated() {
		return this.isTerminated;
	}
	
	/**
	 * Set the state of the space entity to active or terminated.
	 * 
	 * @param 	terminated
	 * 			The new state of the space entity
	 * @post	The state of the space entity is set to the given value.
	 * 			| new.isTerminated() == terminated
	 */
	public void setTerminated(boolean terminated) {
		this.isTerminated = terminated;
	}
		
	/**
	 * Terminate this space entity.
	 * 
	 * @post	If this space entity is not yet terminated and has a not-null world that contains this space entity,
	 * 			the world does not longer contain this space entity, the world of this space entity is set to null and
	 * 			the state of the ship is set to terminated.
	 * 			| if(!isTerminated() && this.getWorld() != null && this.getWorld().hasAsSpaceEntity(this))
	 * 			|	then getWorld().hasAsSpaceEntity(this) == false &&
	 * 			|		 new.getWorld() == null &&
	 * 			|		 new.isTerminated() == true
	 */
	public void terminate() {
		if (!isTerminated() && this.getWorld() != null && this.getWorld().hasAsSpaceEntity(this)) {
			this.getWorld().removeSpaceEntity(this);
			this.setWorld(null);
			setTerminated(true);
		}
	}
	
	/**
	 * This space entity and the given space entity bounce off each other.That is, the
	 * velocity of both space entities is updated to reflect the collision.
	 * 
	 * @param 	spaceEntity
	 * 			The space entity that bounces off this space entity.
	 * @note	For simplicity, the following variables are defined:
	 * 			| deltaR = spaceEntity.getPosition() - this.getPosition()
	 * 			| deltaV = spaceEntity.getVelocity() - this.getVelocity()
	 * 			| sigma = this.getRadius() + spaceEntity.getRadius()
	 * 			| j = 2 * this.getMass() * spaceEntity.getMass() * Vector.dotProduct(deltaV, deltaR) / (sigma * (this.getMass() + spaceEntity.getMass()))
	 * @post	The velocities of this space entity are updated based on their current velocities and their mass. If the resulting velocities generate an invalid speed,
	 * 			the speed is set to the maximum speed while maintaining the updated ratio of the velocities. 
	 * 			| (new this).getVelocity().getX() == getVelocity().getX() + (j * deltaR.getX() / sigma) / this.getMass()
	 * 			| (new this).getVelocity().getY() == getVelocity().getY() + (j * deltaR.getY() / sigma) / this.getMass()
	 * 			| if(!isValidVelocity((new this).getVelocity().getX(), (new this).getVelocity().getY())
	 * 			|	then (new this).setMaxSpeedWithSameDirection()
	 * @post	The velocities of the given space entity are updated based on their current velocities and their mass. If the resulting velocities generate an invalid speed,
	 * 			the speed is set to the maximum speed while maintaining the updated ratio of the velocities. 
	 * 			| (new spaceEntity).getVelocity().getX() == spaceEntity.getVelocity().getX() + (j * deltaR.getX() / sigma) / spaceEntity.getMass()
	 * 			| (new spaceEntity).getVelocity().getY() == spaceEntity.getVelocity().getY() + (j * deltaR.getY() / sigma) / spaceEntity.getMass()
	 * 			| if(!isValidVelocity((new spaceEntity).getVelocity().getX(), (new spaceEntity).getVelocity().getY())
	 * 			|	then (new spaceEntity).setMaxSpeedWithSameDirection()
	 */
	public void bounceWith(SpaceEntity spaceEntity) {
		if(Util.fuzzyEquals(this.getDistanceBetween(spaceEntity), 0.0)){
			Vector deltaR = Vector.substract(spaceEntity.getPosition(), this.getPosition());
			Vector deltaV = Vector.substract(spaceEntity.getVelocity(), this.getVelocity());
			double sigma = this.getRadius() + spaceEntity.getRadius();
			double j = 2 * this.getMass() * spaceEntity.getMass() * Vector.dotProduct(deltaV, deltaR) / (sigma * (this.getMass() + spaceEntity.getMass()));
			double jX = j * deltaR.getX() / sigma;
			double jY = j * deltaR.getY() / sigma;
			this.setVelocity(this.getVelocity().getX() + jX / this.getMass(), this.getVelocity().getY() + jY / this.getMass());
			spaceEntity.setVelocity(spaceEntity.getVelocity().getX() - jX / spaceEntity.getMass(), spaceEntity.getVelocity().getY() - jY / spaceEntity.getMass());
			
			if(!isValidVelocity(this.getVelocity().getX(), this.getVelocity().getY())){
				this.setMaxSpeedWithSameDirection();
			}
			if(!isValidVelocity(spaceEntity.getVelocity().getX(), spaceEntity.getVelocity().getY())){
				spaceEntity.setMaxSpeedWithSameDirection();
			}
		}
	}
	
	/**
	 * Return whether this space entity is the source of the given space entity, if the latter is a Bullet.
	 * 
	 * @param 	spaceEntity
	 * 			The potential bullet of this space entity.
	 * @return	If the given space entity is not of the type Bullet, the result is false.
	 * 			| if(spaceEntity.getClass() != Bullet.class) then result == false
	 * @return	If the given space entity is of the type Bullet, but this space entity is not the source of
	 * 			the given space entity, the result is false.
	 * 			| if(spaceEntity.getClass() == Bullet.class && ((Bullet) spaceEntity).getSource() != this)
	 * 			|	then result == false
	 * @return	If the given space entity is of the type Bullet and this space entity is the source of the
	 * 			given space entity, the result is true.
	 * 			| if(spaceEntity.getClass() == Bullet.class && ((Bullet) spaceEntity).getSource() == this)
	 * 			|	then result == true
	 */
	public boolean isSourceOf(SpaceEntity spaceEntity) {
		if(spaceEntity.getClass() == Bullet.class && ((Bullet) spaceEntity).getSource() == this)
			return true;
		else 
			return false;
	}
	
	/**
	 * Set the general velocity to the maximum speed while maintaining the ratio of the velocities in
	 * respectively x- and y-direction.
	 * 
	 * @post	The new general velocity equals the maximum speed.
	 * 			| new.getGeneralVelocity() == getMaxSpeed()
	 * @post	The ratio of the velocities remain the same.
	 * 			| new.getVelocity().getY()/new.getVelocity().getX() == getVelocity().getX()/getVelocity().getX()
	 */
	public void setMaxSpeedWithSameDirection() {
		double angle = Math.atan(this.getVelocity().getY() / this.getVelocity().getX());
		setVelocity(getMaxSpeed() * Math.cos(angle), getMaxSpeed() * Math.sin(angle));
	}
}
